// file: arch/x86/arch/smbios.c
// autor: jiangxinpeng
// time: 2021.2.16
// copyright: (C) 2020-2050 by jiangxinpeng,All right are reserved.

#include <arch/smbios.h>
#include <os/memcache.h>
#include <os/debug.h>
#include <lib/string.h>
#include <lib/stdio.h>
#include <lib/stdlib.h>

//#define DEBUG_SMBIOS 1

// smbios entry struct
smbios_t *smbios = NULL;

// get entry point
// point to string "_SM_" then figrue and check chesum to get actual entry
void *SmBiosGetEntry()
{
    uint8_t *mem = (uint8_t *)BIOS_BASE_START;
    uint8_t len;
    uint8_t checksum;

    while ((uint32_t)mem < BIOS_BASE_END)
    {
        // check signature
        if (!memcmp(mem, SMBIOS_EPT_SIGN, 4))
        {
            // try to get EPTlen
            len = mem[5];
            // checksum is all byte add,low 8bits should equal 0
            for (int i = 0; i < len; i++)
            {
                checksum += mem[i];
            }
            // checksum no true
            if (!checksum)
                return mem;
        }
        // try start next search
        mem += 16;
    }
    return NULL;
}

char *SmbiosGetVerStr(char *ver, int len)
{
    uint8_t major = smbios->major_version;
    uint8_t minor = smbios->minor_version;
    uint8_t buff[32];

    memset(ver, 0, len);

    strcpy(ver, itoa(major, 10, buff, 0));
    strcat(ver, ".");
    strcat(ver, itoa(minor, 10, buff, 0));

    return ver;
}

void SmbiosInit()
{
    // get entry
    smbios = SmBiosGetEntry() + PAGE_HIGH_BASE;

    KPrint("found smbios at %x\n", smbios);
#if DEBUG_SMBIOS
    SmbiosDump();
#endif
}

#if DEBUG_SMBIOS
void SmbiosDump()
{
    char ver[8];

    for (int i = 0; i < 4; i++)
    {
        KPrint("%c", smbios->entrystring1[i]);
    }
    KPrint("\n");
    KPrint("checksum1: %x\n", smbios->checksum1);
    KPrint("length: %d\n", smbios->length);
    KPrint("ver: %s\n", SmbiosGetVerStr(ver, 8));
    KPrint("max size: %d\n", smbios->max_size);
    KPrint("smbios revision: %x\n", smbios->smbios_revision);
    KPrint("format area: ");
    for (int i = 0; i < 5; i++)
    {
        KPrint("%x", smbios->formatted_area[i]);
    }
    KPrint("\n");
    KPrint("smbios entry2:");
    for (int i = 0; i < 5; i++)
    {
        KPrint("%c", smbios->entrystring2[i]);
    }
    KPrint("\n");
    KPrint("checksum2: %x\n", smbios->checksum2);
    KPrint("table length: %d,table point: %x,table num: %d\n", smbios->table_total_len, smbios->table_point, smbios->table_num);
    KPrint("bcd revision: %x\n", smbios->bcd_revision);

    SmbiosBiosPrint();
    SmbiosBaseboardPrint();
    SmbiosSystemPrint();
    SmbiosSytemEnclosureClassic();
}
#endif

smbios_struct_header_t *SmbiosGetTable(smbios_t *smbios, int type)
{
    uint8_t *p = (uint8_t *)(smbios->table_point + PAGE_HIGH_BASE);
    smbios_struct_header_t *header;
    uint32_t limit = (uint32_t)((uint32_t)smbios->table_total_len + (uint32_t)p);
    int i;
#ifdef __ARCH_X86
    uint32_t count = smbios->table_num;
#endif

    if (!smbios)
        return NULL;

#ifdef __ARCH_X86
    for (i = 0; i < count; i++)
    {
        header = (smbios_struct_header_t *)p;

        if ((uint32_t)p >= limit)
        {
            KPrint("[smbios] Smbios struct table limit fault!\n");
            return NULL;
        }

        if (header->length)
        {
            if (header->type == type)
            {
                KPrint("[smbios] Smbios struct table [%d] found! type:%d len:%d handler:%d\n", type, header->type, header->length, header->handler);
                return header;
            }
        }

        // goto next table
        p += header->length;
        while ((uint32_t)p <= limit)
        {
            if (*p == 0x00 && *(p + 1) == 0x00)
            {
                p += 2;
                break;
            }
            p++;
        }
    }
#else
    while (header < limit)
    {
        header = p;

        if (header->length)
        {
            if (header->type == type)
            {
                KPrint("[smbios] Smbios struct table [%d] found! type:%d len:%d handler:%d\n", type, header->type, header->length, header->handler);
                return header;
            }
        }
        // goto next table
        p += header->length;
        while (p < limit)
        {
            if (*(uint32_t *)p == 0x0000)
                p += 2;
            p++;
        }
    }
#endif
    KPrint("[smbios] No found targe smbios struct table! type:%d\n", type);
    return NULL;
}

char *SmbiosGetString(smbios_struct_header_t *header)
{
    uint8_t *p = (uint8_t *)header;

    if (header->length)
        return p + header->length;
}

char *SmbiosFindStr(smbios_struct_header_t *header, int idx)
{
    uint32_t len = SmbiosGetStrLen(header); // get totol string length of struct table
    uint8_t *p = SmbiosGetString(header);   // get start of string

    if (!idx)
        return NULL;

    while (p < p + len)
    {
        if (idx == 1)
        {
            return p;
        }

        if (*p == '\0')
        {
            idx--;
        }
        p++;
    }
    return NULL;
}

uint32_t SmbiosGetStrLen(smbios_struct_header_t *header)
{
    uint8_t *p = (uint8_t *)header;

    if (header->length)
    {
        p += header->length;

        uint8_t *t = p;
        uint32_t limit = smbios->table_point + PAGE_HIGH_BASE + smbios->table_total_len;
        while ((uint32_t)t < limit)
        {
            if (*t == 0x00 && *(t + 1) == 0x00)
            {
                t += 2;
                return t - (p + 1);
            }
            t++;
        }
    }
    return 0;
}

#if DEBUG_SMBIOS

void SmbiosSytemEnclosureClassic()
{
    smbios_struct_header_t *h = SmbiosGetTable(smbios, System_Enclosure);
    smbios_table_system_enclosure_classic_t *enclosure = h;
    if (!h)
        return;

    KPrint("System Enclosure or Classic Info\n");
    KPrint("-----------------------------------------------\n");
    KPrint("type: %x\n", enclosure->enclosure_type);
    KPrint("version: %s\n", SmbiosFindStr(h, enclosure->version) ?: "NULL");
    KPrint("Serial num: %s\n", SmbiosFindStr(h, enclosure->serial_num) ?: "NULL");
    KPrint("Asset Tag: %s\n", SmbiosFindStr(h, enclosure->asset_tag) ?: "NULL");
    KPrint("Bootup Status: %x\n", enclosure->bootup_status);
    KPrint("Power Status: %x\n", enclosure->power_status);
    KPrint("Thermal Status: %x\n", enclosure->thermal_status);
    KPrint("Security Status: %x\n", enclosure->security_status);
    KPrint("Oem: %x\n", enclosure->oem);
    KPrint("Height: %d\n", enclosure->height);
    KPrint("Number Of Power Cord: %d\n", enclosure->num_of_power);
}

void SmbiosBiosPrint()
{
    smbios_struct_header_t *h = SmbiosGetTable(smbios, BIOS_Info);
    smbios_table_bios_t *bios = h;

    if (!h)
        return;
    KPrint("BIOS Info\n");
    KPrint("-----------------------------------------------\n");
    KPrint("Vendor: %s\n", SmbiosFindStr(h, bios->vendor) ?: "NULL");
    KPrint("Version: %s\n", SmbiosFindStr(h, bios->version) ?: "NULL");
    KPrint("Start addr: %x\n", (uint32_t)bios->start_addr);
    KPrint("Release data: %s\n", SmbiosFindStr(h, bios->release_date) ?: "NULL");
    KPrint("ROM size: %d\n", (uint32_t)bios->rom_size);
    KPrint("support: %x\n", (uint32_t)bios->support);
    KPrint("Bios extension: %x\n", bios->extension[0]);
    KPrint("Bios extension2: %x\n", bios->extension[1]);
    KPrint("system major: %d\n", (uint32_t)bios->sysbios_major_release);
    KPrint("system minor: %d\n", (uint32_t)bios->sysbios_minor_release);
    KPrint("Emabedded major: %d\n", (uint32_t)bios->ctrlfw_major_release);
    KPrint("Emabedded minjor: %d\n", (uint32_t)bios->ctrlfw_minor_release);
    KPrint("-------------------------------------------------\n");
}

void SmbiosSystemPrint()
{
    smbios_struct_header_t *h = SmbiosGetTable(smbios, System_Info);
    smbios_table_syteminfo_t *system = h;
    if (!h)
        return;
    KPrint("System Info\n");
    KPrint("----------------------------------------------\n");
    KPrint("Manufacturer: %s\n", SmbiosFindStr(h, system->manufacturer) ?: "NULL");
    KPrint("Product: %s\n", SmbiosFindStr(h, system->product_name) ?: "NULL");
    KPrint("Version: %s\n", SmbiosFindStr(h, system->version) ?: "NULL");
    KPrint("Serial num: %s\n", SmbiosFindStr(h, system->serial_number) ?: "NULL");
    KPrint("UUID:");
    for (int i = 0; i < 16; i++)
    {
        KPrint("%x", (uint32_t)system->UUID[i]);
    }
    KPrint("\n");
    KPrint("WakeUP: %x\n", system->wakeup_type);
    KPrint("SKU number: %s\n", SmbiosFindStr(h, system->SKU_number) ?: "NULL");
    KPrint("Family: %s\n", SmbiosFindStr(h, system->family) ?: "NULL");
    KPrint("----------------------------------------------\n");
}

void SmbiosBaseboardPrint()
{
    smbios_struct_header_t *h = SmbiosGetTable(smbios, Baseboard_Info);
    smbios_table_baseboard_info_t *baseboard = h;
    if (!h)
        return;
    KPrint("BaseBoard Info\n");
    KPrint("--------------------------------------------\n");
    KPrint("Manufacturer: %s\n", SmbiosFindStr(h, baseboard->manufacturer) ?: "NULL");
    KPrint("Product: %s\n", SmbiosFindStr(h, baseboard->product) ?: "NULL");
    KPrint("Version: %s\n", SmbiosFindStr(h, baseboard->version) ?: "NULL");
    KPrint("Serial num: %s\n", SmbiosFindStr(h, baseboard->serial_number) ?: "NULL");
    KPrint("Asset Tag: %s\n", SmbiosFindStr(h, baseboard->asset_tag) ?: "NULL");
    KPrint("Feature: %x\n", baseboard->feature_flags);
    KPrint("Location in Chassis: %s\n", SmbiosFindStr(h, baseboard->location_chassic) ?: "NULL");
    KPrint("Chassis: %x\n", baseboard->chassic_handle);
    KPrint("BoardType: %d\n", baseboard->board_type);
    KPrint("---------------------------------------------\n");
}

#endif